<script>
import * as d3 from 'd3';


export default {
    data() {
        return {
            // 图的尺寸
            width: 800,
            height: 600,
            // 节点和链接数据
            nodes: [
                { id: "1", label: "节点1", group: "group1" }, { id: "2", label: "节点2", group: "group2" }, { id: "3", label: "节点3", group: "group3" }
            ],
            links: [
                { source: "1", target: "2", name: "属于", category: "属于" }, { source: "2", target: "3", name: "使用", category: "使用" }
            ],
            // 其他数据属性...
            selectedNode: null,
            contextMenu: {
                visible: false,
                x: 0,
                y: 0,
                targetNode: null, // 保存右键点击的节点
            },
            simulation: null,
        };
    },
    mounted() {
        this.createForceGraph();
    },
    watch: {
        // 监听nodes和links的变化，并调用updateGraph进行更新
        nodes: {
            handler: 'updateGraph',
            deep: true,
        },
        links: {
            handler: 'updateGraph',
            deep: true,
        },
    },
    methods: {


        // 计算给定节点的所有直接和间接连接
        computeConnections(node) {
            let connections = new Set([node.id]); // 包括节点自身
            let toProcess = [node.id];

            while (toProcess.length > 0) {
                const currentId = toProcess.pop();
                this.links.forEach(link => {
                    if (link.source.id === currentId && !connections.has(link.target.id)) {
                        connections.add(link.target.id);
                        toProcess.push(link.target.id);
                    } else if (link.target.id === currentId && !connections.has(link.source.id)) {
                        connections.add(link.source.id);
                        toProcess.push(link.source.id);
                    }
                });
            }
            return connections;
        },
        createForceGraph() {
            const svg = d3.select(this.$refs.svg);
            const colorScale = d3.scaleOrdinal(d3.schemeCategory10); // 类别颜色比例尺

            // 初始化力模拟
            this.simulation = d3.forceSimulation(this.nodes)
                .force("link", d3.forceLink(this.links).id(d => d.id).distance(150)) // 节点距离放大3倍
                .force("charge", d3.forceManyBody())
                .force("center", d3.forceCenter(this.width / 2, this.height / 2));


            const labels = svg.append("g")
                .attr("class", "labels")
                .selectAll("text")
                .data(this.nodes)
                .enter().append("text")
                .text(d => d.label) // 假设每个节点对象有一个id属性作为名称
                .attr("x", d => d.x)
                .attr("y", d => d.y)
                .attr("dx", 12) // 文本相对于节点中心的偏移量
                .attr("dy", ".35em") // 垂直对齐方式
                .style("fill", "#555") // 文本颜色
                .style("font-size", "12px"); // 文本大小

            // 绘制连线和名称
            const link = svg.append("g")
                .attr("stroke", "#999")
                .selectAll("g")
                .data(this.links)
                .enter().append("g");

            // 绘制直线连线
            // link.append("line")
            //     .attr("stroke-width", d => Math.sqrt(d.value))
            //     .attr("marker-end", "url(#arrow)") // 使用箭头
            //     .attr("stroke", d => colorScale(d.category)); // 按类别分配颜色


            // 绘制曲线连线
            link.append("path")
                .attr("fill", "none")
                .attr("stroke", d => colorScale(d.category))
                .attr("stroke-width", 2)
                // .attr("marker-end", "url(#arrow)") // 使用箭头
                .attr("marker-end", d => `url(#${this.createArrowMarker(colorScale(d.category))})`)// 应用对应颜色的箭头标记
                .attr("d", d => {
                    const dx = d.target.x - d.source.x,
                        dy = d.target.y - d.source.y,
                        dr = Math.sqrt(dx * dx + dy * dy);
                    // 计算目标节点半径，这里假设为固定值10，可以根据实际情况调整
                    const radius = 10;

                    // 调整连线终点坐标，使其停在节点的边缘
                    const offsetX = (dx * radius) / dr;
                    const offsetY = (dy * radius) / dr;

                    //  生成二次贝塞尔曲线路径，使用调整后的终点坐标绘制连线
                    return `M${d.source.x},${d.source.y}A${dr},${dr} 0 0,1 ${d.target.x - offsetX},${d.target.y - offsetY}`;
                });

            link.append("text")
                .text(d => d.name)
                .attr("x", d => (d.source.x + d.target.x) / 2)
                .attr("y", d => (d.source.y + d.target.y) / 2);

            // 绘制节点
            const node = svg.append("g")
                .attr("stroke", "#fff")
                .attr("stroke-width", 1.5)
                .selectAll("circle")
                .data(this.nodes)
                .enter().append("circle")
                .attr("r", 10)
                .attr("fill", d => colorScale(d.group))
                .call(drag(this.simulation))
                .on("click", (event, d) => {
                    this.selectedNode = d;
                })
                .on("contextmenu", (event, d) => {
                    event.preventDefault();
                    this.contextMenu.x = event.clientX;
                    this.contextMenu.y = event.clientY;
                    this.contextMenu.visible = true;
                    this.contextMenu.targetNode = d; // 设置当前操作的节点为右键点击的节点
                });
            node.exit().remove();

            // 更新位置
            this.simulation.on("tick", () => {
                // 更新文本标签的位置
                labels
                    .attr("x", d => d.x)
                    .attr("y", d => d.y);

                link.selectAll("path")
                    .attr("d", d => {
                        const dx = d.target.x - d.source.x,
                            dy = d.target.y - d.source.y,
                            dr = Math.sqrt(dx * dx + dy * dy);
                        const radius = 10; // 目标节点的半径
                        const offsetX = (dx * radius) / dr;
                        const offsetY = (dy * radius) / dr;
                        return `M${d.source.x},${d.source.y}A${dr},${dr} 0 0,1 ${d.target.x - offsetX},${d.target.y - offsetY}`;

                        // return `M${d.source.x},${d.source.y}A${dr},${dr} 0 0,1 ${d.target.x},${d.target.y}`;
                    });

                link.selectAll("text")
                    .attr("x", d => (d.source.x + d.target.x) / 2)
                    .attr("y", d => (d.source.y + d.target.y) / 2);

                node.attr("cx", d => d.x)
                    .attr("cy", d => d.y);
            });

            // 实现拖拽功能
            function drag(simulation) {

                function dragstarted(event) {
                    simulation.alphaTarget(0.3).restart();
                    event.subject.fx = event.subject.x;
                    event.subject.fy = event.subject.y;
                }

                function dragged(event) {
                    event.subject.fx = event.x;
                    event.subject.fy = event.y;
                }

                function dragended(event) {
                    simulation.alphaTarget(0);
                    event.subject.fx = null;
                    event.subject.fy = null;
                }

                return d3.drag()
                    .on("start", dragstarted)
                    .on("drag", dragged)
                    .on("end", dragended);
            }

        },
        refreshForceGraph() {
            // d3.select(this.$refs.svg).remove();
            d3.select(this.$refs.svg).selectAll("*").remove();
            this.createForceGraph();
        },
        showContextMenu(event) {
            this.contextMenu.x = event.clientX;
            this.contextMenu.y = event.clientY;
            this.contextMenu.visible = true;

            // 识别点击目标，判断是空白处还是节点，并据此调整菜单选项
            // 这里简化处理，仅展示基础逻辑
            this.contextMenu.targetNode = null; // 假设没有点击到节点
        },
        createNode() {
            // 创建节点的逻辑
            this.contextMenu.visible = false;
            const newNodeId = `node${this.nodes.length + 1}`; // 简单的示例ID生成逻辑
            const newNode = { id: newNodeId };
            this.nodes.push(newNode);
            this.refreshForceGraph();
        },
        modifyNode(nodeId, newName) {
            // 修改节点的逻辑，使用 this.contextMenu.targetNode 作为当前操作的节点
            this.contextMenu.visible = false;
            const node = this.nodes.find(node => node.id === nodeId);
            if (node) {
                node.name = newName; // 假设节点有一个 `name` 属性
                this.refreshForceGraph();
            }
        },
        deleteNode(nodeId) {
            // 删除节点的逻辑
            this.contextMenu.visible = false;
            this.nodes = this.nodes.filter(node => node.id !== nodeId);
            this.links = this.links.filter(link => link.source.id !== nodeId && link.target.id !== nodeId);
            this.refreshForceGraph();
        },
        createLink(sourceId, targetId) {
            // 创建连线的逻辑
            this.contextMenu.visible = false;
            const newLink = { source: sourceId, target: targetId };
            this.links.push(newLink);
            this.refreshForceGraph();
        },
        deleteLink(sourceId, targetId) {
            // 删除连线的逻辑
            this.contextMenu.visible = false;

            this.links = this.links.filter(link => !(link.source.id === sourceId && link.target.id === targetId));
            this.refreshForceGraph();
        },

        // 在绘制连线之前，根据需要动态创建箭头标记
        createArrowMarker(color) {
            const markerId = `arrow-${color.replace("#", "")}`;
            // 检查是否已经定义了这种颜色的箭头
            if (!d3.select(`#${markerId}`).empty()) {
                return markerId; // 如果已经定义了，直接返回ID
            }
            // 动态创建箭头标记
            d3.select("defs").append("marker")
                .attr("id", markerId)
                .attr("markerWidth", 10)
                .attr("markerHeight", 10)
                .attr("refX", 10)
                .attr("refY", 3)
                .attr("orient", "auto")
                .attr("markerUnits", "strokeWidth")
                .append("path")
                .attr("d", "M0,0 L0,6 L9,3 z")
                .attr("fill", color); // 设置箭头颜色

            return markerId; // 返回创建的箭头标记的ID
        }


    },
};
</script>
<template>

    <svg ref="svg" :width="width" :height="height">
        <!-- 定义箭头 -->
        <defs>
            <marker id="arrow" markerWidth="10" markerHeight="10" refX="10" refY="3" orient="auto"
                markerUnits="strokeWidth">
                <path d="M0,0 L0,6 L9,3 z" fill="#f00" />
            </marker>
        </defs>
    </svg>
    <div v-if="selectedNode" class="node-details">
        <h3>Node Details</h3>
        <p>ID: {{ selectedNode.id }}</p>
        <!-- 更多详情字段 -->
    </div>
    <div v-if="contextMenu.visible" :style="{ top: contextMenu.y + 'px', left: contextMenu.x + 'px' }"
        class="context-menu">
        <button @click="createNode">创建节点</button>
        <button @click="modifyNode">修改节点</button>
        <button @click="deleteNode">删除节点</button>
        <button @click="createLink">创建关系</button>
        <button @click="deleteLink">删除关系</button>
    </div>
</template>

<style>
/* ForceGraph.vue */
line {
    stroke: #999;
}

circle {
    stroke: #fff;
    stroke-width: 1.5px;
}

.context-menu {
    position: absolute;
    background-color: #f9f9f9;
    box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2);
    padding: 12px 16px;
    z-index: 1;
}
</style>